/* STemp - Secure tempfile extension for Ruby
 *
 * Pure Ruby tempfile implementations appear to suffer from well-known race
 * conditions. The 'mkstemp' family of functions now popular on Linux & *BSD
 * attempt to work around these issues. This module makes them available to
 * Ruby programmers on platforms where they are present.
 *
 * Author:: Cian Synnott <cian@nospam@gmail.com>
 * Copyright:: Copyright (c) 2006 Cian Synnott
 * License:: MIT License
 * 
 * Copyright (c) 2006 Cian Synnott
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * $Id: stemp.c 6 2006-11-15 18:25:26Z cian $
 */

#include "ruby.h"

#include <unistd.h>
#include <errno.h>

VALUE mSTemp;

static void raise_systemcallerror(int, char *);

/* call-seq:
 *   STemp.mkdtemp(template) -> String
 *
 * Wraps <tt>mkdtemp(3)</tt> for Ruby. See <tt>man mkdtemp</tt> on your system
 * for exact semantics.
 * 
 * Notes:
 * - <tt>template</tt> is overwritten with the eventual directory name. 
 * - raises a <tt>SystemCallError</tt> if any of the syscalls involved fail.
 *
 */
static VALUE stemp_mkdtemp(VALUE self, VALUE template) {
  VALUE duckstr;
  char *ret;

  SafeStringValue(template);

  duckstr = StringValue(template);

  ret = mkdtemp(RSTRING(duckstr)->ptr);

  if (NULL == ret) raise_systemcallerror(errno, "in mkdtemp");

  return duckstr;
}

/* call-seq:
 *   STemp.mkstemp(template) -> File
 *
 * Wraps <tt>mkstemp(3)</tt> for Ruby. See <tt>man mkstemp</tt> on your system
 * for exact semantics.
 * 
 * Notes:
 * - returns a File object rather than a simple file descriptor.
 * - <tt>template</tt> is overwritten with the eventual directory name. 
 * - raises a <tt>SystemCallError</tt> if any of the syscalls involved fail.
 * - the Debian manpage for <tt>mkstemp</tt> suggests you shouldn't use it, but
 *   rather stick with <tt>tmpfile</tt>. However, I think it's better all
 *   round to have the choice; there are times when you want to know the
 *   tempfile name. 
 */
static VALUE stemp_mkstemp(VALUE self, VALUE template) {
  VALUE duckstr, filedesc, file;
  int fd;

  SafeStringValue(template);

  duckstr = StringValue(template);

  fd = mkstemp(RSTRING(duckstr)->ptr);

  if (-1 == fd) raise_systemcallerror(errno, "in mkstemp");

  filedesc = INT2FIX(fd);

  file = rb_class_new_instance(1, &filedesc,
    rb_const_get(rb_cObject, rb_intern("File")));
  
  return file;
}

/* call-seq:
 *   STemp.tmpfile -> File
 *
 * Wraps <tt>tmpfile(3)</tt> for Ruby. See <tt>man tmpfile</tt> on your system
 * for exact semantics.
 * 
 * Notes:
 * - raises a <tt>SystemCallError</tt> if any of the syscalls involved fail.
 */
static VALUE stemp_tmpfile(VALUE self) {
  VALUE filedesc, file;
  FILE *fp;

  fp = tmpfile();

  if (NULL == fp) raise_systemcallerror(errno, "in tmpfile");

  /* A shame to have to move back to fileno from the FILE *, but there doesn't
   * seem to be a clean way to do this in Ruby's C bindings otherwise.
   * Note that fileno() is not ANSI C, but should be present wherever mkstemp
   * and company are. */
  filedesc = INT2FIX(fileno(fp));

  file = rb_class_new_instance(1, &filedesc,
    rb_const_get(rb_cObject, rb_intern("File")));
  
  return file;
}

/* raise_systemcallerror - Raise a SystemCallError
 *
 * A utility routine for the actual methods defined here.
 * 
 * Args:
 * - num: the system error number, probably errno
 * - msg: a message to attach
 */
static void raise_systemcallerror(int num, char *msg) {
  VALUE excobj, excno, *params;

  /* Construct argument list */
  excno = INT2FIX(num);
  params = &excno;

  excobj = rb_class_new_instance(1, params,
    rb_const_get(rb_cObject, rb_intern("SystemCallError")));

  /* I go about this in a somewhat strange way because of the way
   * SystemCallError works. Its 'new' method creates an object of the
   * appropriate subclass of Errno based on the number you give it. However, 
   * rb_raise takes a _class_, not an instance. */
  rb_raise(rb_funcall(excobj, rb_intern("class"), 0), msg);
}

/* STemp - secure tempfile extension for Ruby
 * 
 * Note that you should set your +umask+ appropriately when using this library:
 * not all implementations of these functions use a reasonable file creation
 * mask by default.
*/
void Init_stemp() {
  mSTemp = rb_define_module("STemp");
  rb_define_singleton_method(mSTemp, "mkdtemp", stemp_mkdtemp, 1);
  rb_define_singleton_method(mSTemp, "mkstemp", stemp_mkstemp, 1);
  rb_define_singleton_method(mSTemp, "tmpfile", stemp_tmpfile, 0);
}
